package com.apex.shared.redis.utils;

import com.apex.shared.redis.exception.RedisException;
import com.apex.shared.redis.exception.RedisException.OperationType;
import jakarta.annotation.Resource;
import org.redisson.api.*;
import org.redisson.api.options.KeysScanOptions;
import org.redisson.client.codec.StringCodec;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.Instant;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
 * Redis工具类，提供对Redis的基本操作。
 * 该类封装了对Redis的基本操作，包括设置、获取、删除等功能。
 *
 * @author Apex
 */
@SuppressWarnings("unchecked")
@Component
public class RedissonUtil {

    @Resource
    private RedissonClient redissonClient;

    // ============================= String类型操作 ============================

    /**
     * 存储字符串值到Redis中。
     *
     * @param key   键
     * @param value 值
     */
    public void set(String key, String value) {
        try {
            redissonClient.getBucket(key, StringCodec.INSTANCE).set(value);
        } catch (Exception e) {
            throw new RedisException("存储字符串值失败", e, OperationType.SET, key, value);
        }
    }

    /**
     * 存储字符串值到Redis中，并设置过期时间。
     *
     * @param key      键
     * @param value    值
     * @param timeout  过期时间
     * @param timeUnit 时间单位
     */
    public void set(String key, String value, long timeout, TimeUnit timeUnit) {
        try {
            RBucket<String> bucket = redissonClient.getBucket(key, StringCodec.INSTANCE);
            bucket.set(value, Duration.ofSeconds(timeUnit.toSeconds(timeout)));
        } catch (Exception e) {
            throw new RedisException("存储字符串值并设置过期时间失败", e, OperationType.SET, key, value, timeUnit.toSeconds(timeout));
        }
    }

    /**
     * 获取Redis中的字符串值。
     *
     * @param key 键
     * @return 值
     */
    public String get(String key) {
        try {
            RBucket<String> bucket = redissonClient.getBucket(key, StringCodec.INSTANCE);
            return bucket.get();
        } catch (Exception e) {
            throw new RedisException("获取字符串值失败", e, OperationType.GET, key);
        }
    }

    // ============================= Object类型操作 ============================

    /**
     * 存储对象到Redis中。
     *
     * @param key   键
     * @param value 值
     */
    public <T> void setObject(String key, T value) {
        try {
            redissonClient.getBucket(key).set(value);
        } catch (Exception e) {
            throw new RedisException("存储对象失败", e, OperationType.SET, key, value);
        }
    }

    /**
     * 存储对象到Redis中，并设置过期时间。
     *
     * @param key      键
     * @param value    值
     * @param timeout  过期时间
     * @param timeUnit 时间单位
     */
    public <T> void setObject(String key, T value, long timeout, TimeUnit timeUnit) {
        try {
            RBucket<T> bucket = redissonClient.getBucket(key);
            bucket.set(value, Duration.ofSeconds(timeUnit.toSeconds(timeout)));
        } catch (Exception e) {
            throw new RedisException("存储对象并设置过期时间失败", e, OperationType.SET, key, value, timeUnit.toSeconds(timeout));
        }
    }

    /**
     * 获取Redis中的对象。
     *
     * @param key 键
     * @return 值
     */
    public <T> T getObject(String key) {
        try {
            RBucket<T> bucket = redissonClient.getBucket(key);
            return bucket.get();
        } catch (Exception e) {
            throw new RedisException("获取对象失败", e, OperationType.GET, key);
        }
    }

    // ============================= Hash类型操作 ============================

    /**
     * 存储值到Hash中。
     *
     * @param key   键
     * @param field hash键
     * @param value 值
     */
    public <T> boolean addToHash(String key, Object field, T value) {
        try {
            return redissonClient.getMap(key).fastPut(field, value);
        } catch (Exception e) {
            throw new RedisException("存储Hash值失败", e, OperationType.HASH, key);
        }
    }

    /**
     * 存储值到Hash中，并设置过期时间。
     *
     * @param key      键
     * @param field    hash键
     * @param value    值
     * @param timeout  过期时间
     * @param timeUnit 时间单位
     */
    public <T> boolean addToHash(String key, Object field, T value, long timeout, TimeUnit timeUnit) {
        try {
            RMap<Object, T> hash = redissonClient.getMap(key);
            boolean fastPut = hash.fastPut(field, value);
            boolean expire = hash.expire(Instant.now().plus(timeout, timeUnit.toChronoUnit()));
            return fastPut && expire;
        } catch (Exception e) {
            throw new RedisException("存储Hash值并设置过期时间失败", e, OperationType.HASH, key);
        }
    }

    /**
     * 获取Hash中的值。
     *
     * @param key   键
     * @param field hash键
     * @return 值
     */
    public <T> T getFromHash(String key, Object field) {
        try {
            return (T) redissonClient.getMap(key).get(field);
        } catch (Exception e) {
            throw new RedisException("获取Hash值失败", e, OperationType.HASH, key);
        }
    }

    /**
     * 获取Hash中的所有值。
     *
     * @param key 键
     * @return 值
     */
    public <T> Map<Object, T> getFromHash(String key) {
        RMap<Object, T> hash = redissonClient.getMap(key);
        return hash.readAllMap();
    }

    /**
     * 更新Hash中的值。
     *
     * @param key   键
     * @param field hash键
     * @param value 值
     * @return 更新成功返回true，否则返回false
     */
    public <T> boolean updateToHash(String key, Object field, T value) {
        RMap<Object, T> hash = redissonClient.getMap(key);
        return hash.fastReplace(field, value);
    }

    /**
     * 删除Hash中的数据。
     *
     * @param key      键
     * @param hashKeys hash键
     * @return 删除成功的数量
     */
    public <T> long removeFromHash(String key, T... hashKeys) {
        return redissonClient.getMap(key).fastRemove(hashKeys);
    }

    // ============================= List类型操作 ============================

    /**
     * 向List中添加值。
     *
     * @param key   键
     * @param value 值
     */
    public <T> boolean addToList(String key, T value) {
        try {
            return redissonClient.getList(key).add(value);
        } catch (Exception e) {
            throw new RedisException("添加List值失败", e, OperationType.LIST, key, value);
        }
    }

    /**
     * 向List中添加多个值。
     *
     * @param key   键
     * @param value 值列表
     */
    public <T> boolean addToList(String key, List<T> value) {
        try {
            return redissonClient.getList(key).addAll(value);
        } catch (Exception e) {
            throw new RedisException("添加多个List值失败", e, OperationType.LIST, key, value);
        }
    }

    /**
     * 向List中添加值，并设置过期时间。
     *
     * @param key      键
     * @param value    值
     * @param timeout  过期时间
     * @param timeUnit 时间单位
     */
    public <T> boolean addToList(String key, T value, long timeout, TimeUnit timeUnit) {
        try {
            RList<T> list = redissonClient.getList(key);
            list.add(value);
            return list.expire(Instant.now().plus(timeout, timeUnit.toChronoUnit()));
        } catch (Exception e) {
            throw new RedisException("添加List值并设置过期时间失败", e, OperationType.LIST, key, value, timeUnit.toSeconds(timeout));
        }
    }

    /**
     * 获取List中的值。
     *
     * @param key   键
     * @param start 起始位置
     * @param end   结束位置
     * @return 值列表
     */
    public <T> List<T> getFromList(String key, int start, int end) {
        try {
            return (List<T>) redissonClient.getList(key).range(start, end);
        } catch (Exception e) {
            throw new RedisException("获取List值失败", e, OperationType.LIST, key);
        }
    }

    /**
     * 获取List中的所有值。
     *
     * @param key 键
     * @return 值
     */
    public <T> List<T> getFromList(String key) {
        RList<T> list = redissonClient.getList(key);
        return list.readAll();
    }

    /**
     * 移除List左侧第一个元素。
     *
     * @param key 键
     */
    public void removeListLeft(String key) {
        redissonClient.getList(key).fastRemove(0);
    }

    /**
     * 移除List右侧第一个元素。
     *
     * @param key 键
     */
    public void removeListRight(String key) {
        RList<Object> list = redissonClient.getList(key);
        list.fastRemove(list.size() - 1);
    }

    /**
     * 移除List指定位置的元素。
     *
     * @param key   键
     * @param index 索引
     */
    public void removeFromList(String key, int index) {
        redissonClient.getList(key).fastRemove(index);
    }

    /**
     * 移除List中的指定元素。
     *
     * @param key   键
     * @param value 值
     */
    public <T> boolean removeFromList(String key, T value) {
        return redissonClient.getList(key).removeIf(o -> o.equals(value));
    }

    // ============================= Set类型操作 ============================

    /**
     * 添加值到Set中。
     *
     * @param key   键
     * @param value 值
     */
    public <T> boolean addToSet(String key, T value) {
        try {
            return redissonClient.getSet(key).add(value);
        } catch (Exception e) {
            throw new RedisException("添加Set值失败", e, OperationType.SET_OPS, key, value);
        }
    }

    /**
     * 添加值到Set中，并设置过期时间。
     *
     * @param key      键
     * @param value    值
     * @param timeout  过期时间
     * @param timeUnit 时间单位
     */
    public <T> boolean addToSet(String key, T value, long timeout, TimeUnit timeUnit) {
        try {
            RSet<T> set = redissonClient.getSet(key);
            boolean added = set.add(value);
            return added && set.expire(Instant.now().plus(timeout, timeUnit.toChronoUnit()));
        } catch (Exception e) {
            throw new RedisException("添加Set值并设置过期时间失败", e, OperationType.SET_OPS, key, value, timeUnit.toSeconds(timeout));
        }
    }

    /**
     * 添加多个值到Set中。
     *
     * @param key    键
     * @param values 值
     * @return 是否成功
     */
    public <T> boolean addToSet(String key, List<T> values) {
        return redissonClient.getSet(key).addAll(values);
    }

    /**
     * 添加多个值到Set中，并设置过期时间。
     *
     * @param key      键
     * @param values   值
     * @param timeout  过期时间
     * @param timeUnit 时间单位
     * @return 是否成功
     */
    public <T> boolean addToSet(String key, List<T> values, long timeout, TimeUnit timeUnit) {
        RSet<T> set = redissonClient.getSet(key);
        set.addAllCounted(values);
        return set.expire(Instant.now().plus(timeout, timeUnit.toChronoUnit()));
    }

    /**
     * 获取Set中的所有元素。
     *
     * @param key 键
     * @return 所有值
     */
    public <T> Set<T> getFromSet(String key) {
        try {
            RSet<T> set = redissonClient.getSet(key);
            return new HashSet<T>(set.readAll());
        } catch (Exception e) {
            throw new RedisException("获取Set值失败", e, OperationType.SET_OPS, key);
        }
    }

    /**
     * 从Set中删除多个值。
     *
     * @param key    键
     * @param values 值
     */
    public <T> void removeFromSet(String key, List<T> values) {
        RSet<T> set = redissonClient.getSet(key);
        values.forEach(set::remove);
    }

    /**
     * 从Set中删除值。
     *
     * @param key   键
     * @param value 值
     */
    public <T> boolean removeFromSet(String key, T value) {
        return redissonClient.getSet(key).remove(value);
    }

    // ============================= ZSet类型操作 ============================

    /**
     * 添加值到ZSet中。
     *
     * @param key   键
     * @param value 值
     * @param score 分值
     */
    public <T> void addToZSet(String key, T value, double score) {
        try {
            redissonClient.getScoredSortedSet(key).add(score, value);
        } catch (Exception e) {
            throw new RedisException("添加ZSet值失败", e, OperationType.ZSET, key, value);
        }
    }

    /**
     * 添加值到ZSet中，并设置过期时间。
     *
     * @param key      键
     * @param value    值
     * @param score    分值
     * @param timeout  过期时间
     * @param timeUnit 时间单位
     */
    public <T> void addToZSet(String key, T value, double score, long timeout, TimeUnit timeUnit) {
        try {
            RScoredSortedSet<T> sortedSet = redissonClient.getScoredSortedSet(key);
            sortedSet.add(score, value);
            sortedSet.expire(Instant.now().plus(timeout, timeUnit.toChronoUnit()));
        } catch (Exception e) {
            throw new RedisException("添加ZSet值并设置过期时间失败", e, OperationType.ZSET, key, value, timeUnit.toSeconds(timeout));
        }
    }

    /**
     * 获取ZSet的范围元素。
     *
     * @param key   键
     * @param start 起始位置
     * @param end   结束位置
     * @return Set类型的值
     */
    public <T> Set<Object> getFromZSet(String key, int start, int end) {
        try {
            return new HashSet<>(redissonClient.getScoredSortedSet(key).valueRange(start, end));
        } catch (Exception e) {
            throw new RedisException("获取ZSet值失败", e, OperationType.ZSET, key);
        }
    }

    /**
     * 从ZSet中删除多个值。
     *
     * @param key    键
     * @param values 值
     */
    public <T> void removeFromZSet(String key, List<T> values) {
        redissonClient.getScoredSortedSet(key).removeAll(values);
    }

    /**
     * 从ZSet中删除值。
     *
     * @param key   键
     * @param value 值
     */
    public <T> void removeFromZSet(String key, T value) {
        redissonClient.getScoredSortedSet(key).remove(value);
    }

    // ============================= Common ============================

    /**
     * 判断Key是否存在。
     *
     * @param key 键
     * @return 存在返回true，否则返回false
     */
    public boolean exists(String key) {
        return redissonClient.getBucket(key).isExists();
    }

    /**
     * 删除Key。
     *
     * @param key 键
     */
    public boolean remove(String key) {
        try {
            return redissonClient.getKeys().delete(key) > 0;
        } catch (Exception e) {
            throw new RedisException("删除键失败", e, OperationType.DELETE, key);
        }
    }

    /**
     * 设置Key的过期时间。
     *
     * @param key      键
     * @param timeout  过期时间
     * @param timeUnit 时间单位
     */
    public void expire(String key, long timeout, TimeUnit timeUnit) {
        try {
            redissonClient.getBucket(key).expire(Instant.now().plus(timeout, timeUnit.toChronoUnit()));
        } catch (Exception e) {
            throw new RedisException("设置过期时间失败", e, OperationType.EXPIRE, key, null, timeUnit.toSeconds(timeout));
        }
    }

    /**
     * 获取Key的过期时间。
     *
     * @param key      键
     * @param timeUnit 时间单位
     * @return 过期时间
     */
    public Long getExpire(String key, TimeUnit timeUnit) {
        try {
            long expireTime = redissonClient.getBucket(key).getExpireTime();
            return timeUnit.convert(expireTime, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            throw new RedisException("获取过期时间失败", e, OperationType.EXPIRE, key);
        }
    }

    /**
     * 递增操作。
     *
     * @param key   键
     * @param delta 增加的值
     * @return 递增后的值，如果键不存在，则返回-1
     */
    public long increment(String key, long delta) {
        try {
            return redissonClient.getAtomicLong(key).addAndGet(delta);
        } catch (Exception e) {
            throw new RedisException("递增操作失败", e, OperationType.INCREMENT, key, delta);
        }
    }

    /**
     * 递减操作。
     *
     * @param key   键
     * @param delta 减少的值
     * @return 递减后的值，如果键不存在，则返回-1
     */
    public long decrement(String key, long delta) {
        try {
            return redissonClient.getAtomicLong(key).decrementAndGet();
        } catch (Exception e) {
            throw new RedisException("递减操作失败", e, OperationType.DECREMENT, key, delta);
        }
    }

    /**
     * 递增操作。
     *
     * @param key   键
     * @param delta 增加的值
     * @return 递增后的值，如果键不存在，则返回-1
     */
    public double increment(String key, double delta) {
        try {
            return redissonClient.getAtomicDouble(key).addAndGet(delta);
        } catch (Exception e) {
            throw new RedisException("递增操作失败", e, OperationType.INCREMENT, key, delta);
        }
    }

    /**
     * 递减操作。
     *
     * @param key   键
     * @param delta 减少的值
     * @return 递减后的值，如果键不存在，则返回-1
     */
    public double decrement(String key, double delta) {
        try {
            return redissonClient.getAtomicDouble(key).decrementAndGet();
        } catch (Exception e) {
            throw new RedisException("递减操作失败", e, OperationType.DECREMENT, key, delta);
        }
    }

    /**
     * 匹配keys
     *
     * @param pattern 匹配键
     * @return 匹配的结果
     */
    public Set<String> scanKeys(String pattern) {
        try {
            KeysScanOptions options = KeysScanOptions.defaults().pattern(pattern);
            return StreamSupport.stream(redissonClient.getKeys().getKeys(options).spliterator(), false)
                    .collect(Collectors.toSet());
        } catch (Exception e) {
            throw new RedisException("扫描键失败", e, OperationType.GET, pattern);
        }
    }
}
